#include "network.hpp"
#include "loger.hpp"

#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

static int __listen(std::string_view ip, int port)
{
    if(port < 1 || port > 65535) {
        log_error("invaild port:{}", port);
        return ERR_LINE;
    }

    int listen_fd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(listen_fd == -1) {
        log_error("call socket failed by {}:{}", errno, std::strerror(errno));
        return ERR_LINE;
    }

    do {
        int reuse = 1;
        if(::setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
               sizeof(reuse)) == -1) {
            log_error("set socket reuse failed by {}:{}", errno,
                std::strerror(errno));
            break;
        }

        struct sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_port   = ::htons(port);

        if(ip == "0.0.0.0") {
            addr.sin_addr.s_addr = INADDR_ANY;
        } else {
            if(::inet_pton(AF_INET, ip.data(), &addr.sin_addr) != 1) {
                log_error(
                    "ip str2bin failed by {}:{}", errno, std::strerror(errno));
                break;
            }
        }

        if(::bind(listen_fd, reinterpret_cast<const struct sockaddr*>(&addr),
               sizeof(addr)) == -1) {
            log_error("call bind failed by {}:{}", errno, std::strerror(errno));
            break;
        }

        constexpr int BACKLOG = 128;
        if(::listen(listen_fd, BACKLOG) == -1) {
            log_error(
                "call listen failed by {}:{}", errno, std::strerror(errno));
            break;
        }

        log_info("new {} listen in {}:{}", listen_fd, ip, port);
        return listen_fd;
    } while(0);

    ::close(listen_fd);
    return ERR_LINE;
}

static int wait_for_socket_ready(
    int sock_fd, size_t timeout_sec, size_t timeout_usec)
{
    fd_set write_fds;
    fd_set except_fds;
    FD_ZERO(&write_fds);
    FD_ZERO(&except_fds);
    FD_SET(sock_fd, &write_fds);
    FD_SET(sock_fd, &except_fds);

    struct timeval timeout{};
    timeout.tv_sec  = timeout_sec;
    timeout.tv_usec = timeout_usec;

    // 等待事件就绪或超时
    int select_ret =
        select(sock_fd + 1, nullptr, &write_fds, &except_fds, &timeout);
    if(select_ret == -1) {
        log_error("call select failed by {}:{}", errno, std::strerror(errno));
        return ERR_LINE;
    } else if(select_ret == 0) {
        log_error("connect timeout by {}:{}", errno, std::strerror(errno));
        return ERR_LINE;
    }

    // 检查是否异常
    int       error     = 0;
    socklen_t error_len = sizeof(error);
    if(getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &error, &error_len) == -1) {
        log_error("call getsockopt SOL_SOCKET failed by {}:{}", errno,
            std::strerror(errno));
        return ERR_LINE;
    }

    if(error != 0) {
        log_error("connect error by {}:{}", error, std::strerror(error));
        return ERR_LINE;
    }

    // 事件就绪
    return 0;
}

static int __connect(std::string_view ip, int port)
{
    if(port < 1 || port > 65535) {
        log_error("invaild port:{}", port);
        return ERR_LINE;
    }

    if(ip.empty() || ip.size() > INET_ADDRSTRLEN) {
        log_error("invaild ip:{}", ip);
        return ERR_LINE;
    }

    int sock_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    if(sock_fd == -1) {
        log_error("create socket failed by {}:{}", errno, std::strerror(errno));
        return ERR_LINE;
    }

    do {
        struct sockaddr_in server_addr{};
        server_addr.sin_family = AF_INET;
        server_addr.sin_port   = ::htons(port);
        if(inet_pton(AF_INET, ip.data(), &server_addr.sin_addr) != 1) {
            log_error(
                "ip str2bin failed by {}:{}", errno, std::strerror(errno));
            break;
        }

        socklen_t addr_len = sizeof(server_addr);
        int       rc       = ::connect(sock_fd,
                        reinterpret_cast<const struct sockaddr*>(&server_addr), addr_len);
        if(rc == 0) {
            return sock_fd;
        } else if(rc == -1 && errno != EINPROGRESS) {
            log_error("connect failed by {}:{}", errno, std::strerror(errno));
            break;
        }

        if(wait_for_socket_ready(sock_fd, 3, 0) < 0) {
            break;
        }

        return sock_fd;
    } while(0);

    return ERR_LINE;
}

class LinuxConnection : public sl::Connection {
  private:
    int fd_ = -1;

  protected:
    size_t on_recv(std::vector<sl::byte>& buffer) override
    {
        return 0;
    }

  public:
    LinuxConnection(int fd): fd_{fd} { };
    ~LinuxConnection()
    {
        if(fd_ > 0) {
            ::close(fd_);
        }
    }

    void async_read() override { }
};

class LinuxAcceptor : public sl::Acceptor {
  private:
    int fd_ = -1;

  public:
    LinuxAcceptor(int fd): fd_{fd} { }
    ~LinuxAcceptor()
    {
        if(fd_ > 0) {
            ::close(fd_);
        }
    }

    virtual std::shared_ptr<sl::Connection> accept() override
    {
        int fd = -1;
        return std::make_shared<LinuxConnection>(fd);
    }
};

std::shared_ptr<sl::Acceptor> listen(int port, std::string_view ip)
{
    int fd = __listen(ip, port);

    return std::make_shared<LinuxAcceptor>(fd);
}

std::shared_ptr<sl::Connection> connect(int port, std::string_view ip)
{
    int fd = __connect(ip, port);

    return std::make_shared<LinuxConnection>(fd);
}
